package com.unitd.frame.mybatis.plugin;

import com.unitd.frame.comm.utils.BeanUtils;
import com.unitd.frame.mybatis.interceptor.IInterceptorHandler;
import com.unitd.frame.mybatis.parser.MybatisMapperParser;
import com.unitd.frame.mybatis.plugin.cache.CacheHandler;
import com.unitd.frame.mybatis.plugin.pagination.PaginationHandler;
import com.unitd.frame.mybatis.plugin.rw.RwRouteHandler;
import com.unitd.frame.mybatis.plugin.shard.DatabaseRouteHandler;
import com.unitd.frame.spring.SpringInstanceFactory;
import com.unitd.frame.spring.SpringInstanceProvider;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * @desc Mybatis 插件入口,获取Mybatis的上下文对象信息并处理
 * @filename MybatisPluginInterceptor.java
 * @copyright www.unitd.com
 * @author Hudan
 * @version 1.0
 * @date 2017/2/5
 */
@Intercepts({
		@Signature(type = Executor.class, method = "update", args = {
				MappedStatement.class, Object.class}),
		@Signature(type = Executor.class, method = "query", args = {
				MappedStatement.class, Object.class, RowBounds.class,
				ResultHandler.class})})
public class MybatisPluginInterceptor implements Interceptor, InitializingBean, DisposableBean, ApplicationContextAware {

	protected static final Logger logger = LoggerFactory.getLogger(MybatisPluginInterceptor.class);
	/** Mybatis相关的属性配置信息 */
	private Properties properties;
	/** 拦截器列表 */
	private List<IInterceptorHandler> interceptorHandlers = new ArrayList<>();
	/** CRUD框架是否支持缓存,读写分离,水平自动分库 */
	private static boolean cacheEnabled, rwRouteEnabled, dbShardEnabled;

	/**
	 * @desc 设置进行处理的拦截器(cache,rwRoute,dbShard)
	 * @param interceptorHandlers 拦截器名称
	 */
	public void setInterceptorHandlers(String interceptorHandlers) {
		// 将定义的spring-mybatis.xml中定义的拦截器名称串按照,分割成字符串数组
		String[] handlerNames = StringUtils.tokenizeToStringArray(interceptorHandlers, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

		for (String name : handlerNames) {
			if ("cache".equals(name)) {
				this.interceptorHandlers.add(new CacheHandler());
				cacheEnabled = true;
			} else if ("rwRoute".equals(name)) {
				this.interceptorHandlers.add(new RwRouteHandler());
				rwRouteEnabled = true;
			} else if ("dbShard".equals(name)) {
				this.interceptorHandlers.add(new DatabaseRouteHandler());
				dbShardEnabled = true;
			} else if ("page".equals(name)) {
				this.interceptorHandlers.add(new PaginationHandler());
			}else{
				//自定义的拦截器
				try {
					Class<?> clazz = Class.forName(name);
					IInterceptorHandler handler = (IInterceptorHandler) clazz.newInstance();
					this.interceptorHandlers.add(handler);
					logger.info("注册自定义的Mybatis拦截器成功: {}", name);
				} catch (Exception e) {
					logger.error("注册自定义的Mybatis拦截器失败: {}", name);
				}
			}

			// 比较定义的多个拦截器的排序号, 按照排序号大小进行排序
			Collections.sort(this.interceptorHandlers, new Comparator<IInterceptorHandler>() {
				@Override
				public int compare(IInterceptorHandler handler1, IInterceptorHandler handler2) {
					return Integer.compare(handler1.interceptorOrder(), handler2.interceptorOrder());
				}
			});
		}
	}

	/**
	 * @desc 设置需要解析的mapper.xml的位置
	 * @param mapperLocations mappere.xml文件位置
	 */
	public void setMapperLocations(String mapperLocations) {
		MybatisMapperParser.setMapperLocations(mapperLocations);
	}

	/**
	 * @desc 拦截器的事件拦截处理方法
	 * @param invocation 拦截器调用者
	 * @return Object
	 */
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		Object result = null;
		boolean proceed = false;

		for (IInterceptorHandler handler : interceptorHandlers) {
			result = handler.onInterceptor(invocation);
			if (result != null) {
				break;
			}
		}

		if (BeanUtils.isNull(result)) {
			result = invocation.proceed();
			proceed = true;
		}

		for (IInterceptorHandler handler : interceptorHandlers) {
			handler.onFinished(invocation, proceed ? result : null);
		}
		return result;
	}

	/**
	 * @desc 重写Mybatis本身的Interceptor的plugin方法
	 * @param target 目标插件对象
	 * @return Object
	 */
	@Override
	public Object plugin(Object target) {
		if (target instanceof Executor) {
			return Plugin.wrap(target, this);
		} else {
			return target;
		}
	}

	/**
	 * @desc 重写Mybatis本身的Interceptor的setProperties方法,获取配置属性
	 * @param properties 配置属性
	 */
	@Override
	public void setProperties(Properties properties) {
		this.properties = properties;
	}

	/**
	 * @desc 实现ApplicationContextAware接口, 重写设置Spring上下文对象
	 * @param applicationContext Spring上下文对象
	 */
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		SpringInstanceFactory.setSpringInstanceProvider(new SpringInstanceProvider(applicationContext));
	}

	/**
	 * @desc 实现InitializingBean接口, 重写读取properties属性文件后的处理方法
	 */
	@Override
	public void afterPropertiesSet() {
		for (IInterceptorHandler handler : interceptorHandlers) {
			handler.start(this);
		}
	}

	/**
	 * @desc 继承Spring的DisposableBean类的destroy方法,每次调用完成后,都会去自动调用此方法
	 */
	@Override
	public void destroy() throws Exception {
		for (IInterceptorHandler handler : interceptorHandlers) {
			handler.close();
		}
	}

	public String getProperty(String key) {
		return properties == null ? null : properties.getProperty(key);
	}

	public String getProperty(String key, String defaultVal) {
		String property = getProperty(key);
		return property == null ? defaultVal : property;
	}

	public static boolean isCacheEnabled() {
		return cacheEnabled;
	}

	public static boolean isRwRouteEnabled() {
		return rwRouteEnabled;
	}

	public static boolean isDbShardEnabled() {
		return dbShardEnabled;
	}
}
